﻿using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.MemoryMappedFiles;
using System.Linq;
using System.Security.AccessControl;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace MebAppLib
{
    /// <summary>
    /// 跨进程/线程共享数据
    /// </summary>
    public class SharedMemroy
    {
        /// <summary>
        /// 写入数据供其它线程/进程使用
        /// </summary>
        /// <param name="id"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static SharedMemoryMutexHandle SetData(string id, object value)
        {
            var json = JsonConvert.SerializeObject(value);
            var data = Encoding.UTF8.GetBytes(json);
            return WriteInternal(id, data);
        }

        /// <summary>
        /// 读取其他线程/进程写入的数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="id"></param>
        /// <returns></returns>
        public static T GetData<T>(string id)
        {
            var data = ReadInternal(id);
            var json = Encoding.UTF8.GetString(data);
            return JsonConvert.DeserializeObject<T>(json);
        }

        private static SharedMemoryMutexHandle WriteInternal(string id, byte[] data)
        {
            MemoryMappedFile mmf = MemoryMappedFile.CreateOrOpen(id, 1024000);
            {
                Mutex mutex = null;
                try
                {
                    mutex = CreateMutex(id + "mutex");
                }
                catch (UnauthorizedAccessException)
                {
                    mutex = OpenMutex(id + "mutex");
                    mutex.WaitOne();
                }
                using (MemoryMappedViewStream stream = mmf.CreateViewStream()) //创建文件内存视图流
                {
                    var writer = new BinaryWriter(stream);
                    writer.Write(data.Length);
                    writer.Write(data);
                }
                mutex.ReleaseMutex();
            }
            return new SharedMemoryMutexHandle(id);
        }

        private static byte[] ReadInternal(string id)
        {
            using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(id))
            {
                using (var mutex = OpenMutex(id + "mutex"))
                {
                    mutex.WaitOne();
                    byte[] data = null;
                    using (MemoryMappedViewStream stream = mmf.CreateViewStream()) //注意这里的偏移量
                    {
                        var reader = new BinaryReader(stream);
                        var len = reader.ReadInt32();
                        if (len == 0)
                            data = null;
                        else
                            data = reader.ReadBytes(len);
                    }
                    mutex.ReleaseMutex();
                    return data;
                }
            }
        }

        internal static Mutex OpenMutex(string id, bool owner = false)
        {
            Mutex m = Mutex.OpenExisting(id, MutexRights.ReadPermissions | MutexRights.ChangePermissions);
            // Get the current ACL. This requires 
            // MutexRights.ReadPermissions.
            MutexSecurity mSec = m.GetAccessControl();

            string user = Environment.UserDomainName + "\\"
                + Environment.UserName;

            // First, the rule that denied the current user 
            // the right to enter and release the mutex must
            // be removed.
            MutexAccessRule rule = new MutexAccessRule(user,
                 MutexRights.Synchronize | MutexRights.Modify,
                 AccessControlType.Deny);
            var ok = mSec.RemoveAccessRule(rule);

            // Now grant the user the correct rights.
            // 
            if (owner)
            {
                rule = new MutexAccessRule(user,
                    MutexRights.FullControl,
                    AccessControlType.Allow);
            }
            else
                rule = new MutexAccessRule(user,
                    MutexRights.Synchronize | MutexRights.Modify,
                    AccessControlType.Allow);
            mSec.AddAccessRule(rule);

            // Update the ACL. This requires
            // MutexRights.ChangePermissions.
            m.SetAccessControl(mSec);
            if (owner)
            {
                m = Mutex.OpenExisting(id, MutexRights.FullControl);
            }
            else
                m = Mutex.OpenExisting(id);
            return m;
        }

        private static Mutex CreateMutex(string id)
        {
            string user = Environment.UserDomainName + "\\"
                + Environment.UserName;
            MutexSecurity mSec = new MutexSecurity();

            MutexAccessRule rule = new MutexAccessRule(user,
                MutexRights.Synchronize | MutexRights.Modify,
                AccessControlType.Deny);
            mSec.AddAccessRule(rule);

            rule = new MutexAccessRule(user,
                MutexRights.ReadPermissions | MutexRights.ChangePermissions,
                AccessControlType.Allow);
            mSec.AddAccessRule(rule);
            bool mutexCreated;
            return new Mutex(true, id, out mutexCreated, mSec);
        }
    }
}
